import qbs
import qbs.File
import qbs.FileInfo
import qbs.TextFile
import qbs.Xml

import "CommonProduct.qbs" as CommonProduct

import "js/wixInstaller.js" as Wix

/**
  WixInstaller product.

  This product creates Windows installers with the aid of WiX Toolset. You can get WiX Toolset from [here](https://wixtoolset.org/).

  To create the installer specify product(s) you would want to install with `Depends` item. All of its dpendencies will be listed in
  `wxs` file and pulled to the resulting `msi` by WiX linker.

  WixInstaller item can also understand artifacts created by `cutehmi.windeployqt` Qbs module to pull Qt runtime into resulting `msi`.
  Module `cutehmi.windeployqt` can be enabled globally with `windeployqt` property defined in `CuteHMI.qbs`. The property can be
  modified by editting the file locally, but product, project and module properties can be also [overriden from command line](https://doc.qt.io/qbs/language-introduction.html#overriding-property-values-from-the-command-line).

  For complete runtime you may need to specify MSVC merge modules and 3rd party libraries. WixInstaller rule packs to `msi` all
  files marked with "installable" tag from products it depends on. Merge modules can be specified with `mergeModules` property.

  Note that WixInstaller products are not built by default.

  You may want to set `destinationDirectory` property to move artifacts in desired location.
  */
CommonProduct {
	condition: qbs.targetOS.contains("windows")

	builtByDefault: false

	type: ["msi"]

	baseName: isNaN(name.substr(name.lastIndexOf(".", name.length - 1) + 1)) ? name : name.substring(0, name.lastIndexOf(".", name.length - 1))

	major: isNaN(name.substr(name.lastIndexOf(".", name.length - 1) + 1)) ? 1 : Number(name.substr(name.lastIndexOf(".", name.length - 1) + 1))

	targetName: name + "-" + minor + "-" + micro

	/**
	  Prefefined id of manufacturer installation directory.
	  */
	readonly property string manufacturerInstallationDirectoryId: "manufacturerInstallationDirectory"

	/**
	  Predefined id of start menu directory.
	  */
	readonly property string startMenuDirectoryId: "startMenuDirectory"

	/**
	  List of file tags defining files that should be packed into resulting `msi`. This list rather does not need to be modified.
	  */
	property stringList inputFileTags: ["installable", "cutehmi.windeployqt.json"]

	/**
	  Default culture (locale) used by the installer.
	  */
	property string defaultCulture: "en-us"

	/**
	  List of cultures as passed to _light_ linker.
	  */
	property string cultures: "en-us"

	/**
	  Translations to be used in generated wxl file for default culture (`defaultCulture` property). If not specified a fallback
	  string for `en-us` culture is being used. Array has the form:

	  @verbatim
	  ({
		"<culture>": {
			<string id>: <translation>
		},
	  })
	  @endverbatim

	  For example Polish translation could look like:
	  @verbatim
	  ({
		"pl-pl": {
			"DowngradeErrorMessage": "Produkt [ProductName] jest już zainstalowany w nowszej wersji. Program instalacyjny zakończy teraz działanie",
			"startMenuShortcut_6a40770c84216f8b0_Name": "My HMI",
		},
	  })
	  @endverbatim

	  Functionality of translating with this property is limitted. Rule generating `wxs` file can recognize only sevaral types of string ids:
	  - DowngradeErrorMessage
	  - ProductName
	  - PackageDescription
	  - <feature id>_Title
	  - <feature id>_Description
	  - <start menu shortcut id>_Name
	  - <desktop shortcut id>_Name

	  Refer to generated `cutehmi.wxs` file to find relevant id.

	  The general way of translating messages is by providing appropriate `wxl` file in project files.
	*/
	property var defaultTranslations: ({})

	/**
	  Name of `wxl` artifact with translations for default culture.
	  */
	property path wxlArtifact: "cutehmi_" + defaultCulture + ".wxl"

	/**
	  Name of `wxs` artifact.
	  */
	property path wxsArtifact: "cutehmi.wxs"

	/**
	  The file with UpgradeCode GUID. This code has to be persistent accross releases. It is automatically generated by the
	  WixInstaller item, so there is no need to create one. Because it has to be persistent it's not treated as an artifact and
	  it is a good idea to add this file to version control system.
	  */
	property path wixUpgradeCodeFile: "wix_upgrade_code.guid"

	/**
	  Language of the product being installed. This property refers to Product XML entity in `wxs` file.
	  */
	property int productLanguage: 1033

	/**
	  List of languages supported in the package.
	  */
	property string packageLanguages: productLanguage.toString()

	/**
	  Codepage used in the `wxs` document. Note that WiX has troubles with "UTF-8" (see https://wixtoolset.org/documentation/manual/v3/overview/codepage.html).
	*/
	property string xmlCodepage: "iso-8859-1"

	/**
	  Codepage of the resulting MSI.
	  */
	property string productCodepage: "Windows-1252"

	/**
	  Codepage for the package summary info strings.
	  */
	property string packageSummaryCodepage: productCodepage

	/**
	  Product version. Note that hash is not used and other parts must be numbers.
	  See: https://learn.microsoft.com/en-us/windows/win32/msi/productversion.
	  */
	property string productVersion: major + "." + minor + "." + micro

	/**
	  Product manufacturer.
	  */
	property string productManufacturer: vendor

	/**
	  Package manufacturer.
	  */
	property string packageManufacturer: productManufacturer

	/**
	  Package keywords.
	  */
	property string packageKeywords: undefined

	/**
	  The minimum version of the Windows Installer required to install this package.
	  */
	property string packageInstallerVersion: "450"

	/**
	  Name of the start menu directory for the installed product.
	  */
	property string startMenuDirectory: friendlyName

	// See: https://stackoverflow.com/questions/1929038/compilation-error-ice80-the-64bitcomponent-uses-32bitdirectory
	/**
	  Program files directory id. WiX expects `ProgramFilesFolder` or `ProgramFiles64Folder` depending on the architecture of
	  the binary.
	  */
	property string programFilesFolderId: qbs.architecture === "x86_64" ? "ProgramFiles64Folder" : "ProgramFilesFolder"

	/**
	  Manufacturer installation directory. Files are installed to @a (programFilesFolderId) / @a manufacturerInstallationDirectory / @a installdirDirectory.
	  */
	property string manufacturerInstallationDirectory: productManufacturer

	/**
	  Product installation directory. Files are installed to @a (programFilesFolderId) / @a manufacturerInstallationDirectory / @a installdirDirectory.
	  */
	property string installdirDirectory: friendlyName + " " + productVersion

	/**
	  A list of featured products. List any product names (as defined in Qbs through `name` property) that should appear as the
	  features to isntall. If the list is empty then all products and their components will be installed within a single "root"
	  feature.

	  For Qt runtime gathered with _windeployqt_ you can use special `/QtRuntime/` product name.
	  */
	property stringList featuredProducts: []

	/**
	  A list of ids, which should be included within `<UIRef>` elements. If you use WiX UI extensions you also need to add
	  `-ext WixUIEXtension` to `wix.linkerFlags`. Sample `sampleUI.wxs` file is provided in project root "extra" directory. To use
	  the file refer to `Id` of the `UI` element. For example to include mentioned file the list could look like:

	  @verbatim
		uiRefIds: [
			"sampleUI",
			"WixUI_ErrorProgressText",
		]
	  @endverbatim

	  Custom UI `wxs` file must be listed in project files (but not WiX UI extension files).
	  */
	property stringList uiRefIds: []

	/**
	  A list of ids, which should be included within `<FeatureRef>` elements.
	  */
	property stringList featureRefIds: []

	/**
	  Icon definitions. Property value is a map which has following form:

	  @verbatim
	  ({
		"<icon id>": "<path to icon file>",
	  })
	  @endverbatim

	  Due to weirdness of WiX/Windows Installer icon ids need to have the same "extension" as the file they are used for
	  (e.g., "exe" files need icon with "*.exe" id). So you may need to set something like:

	  @verbatim
	  ({
		"myIcon.exe": sourceDirectory + "/myIcon.ico",
	  })
	  @endverbatim

	*/
	property var icons: ({})

	/**
	  Icon assignemnts. The value is a map which has following form:

	  @verbatim
	  ({
		"<element id>": {
			"Id": "<icon id>",
			["IconIndex": "<index>]
		},
	  })
	  @endverbatim

	  As an example this map sets desktop and start menu shortcut icons and icon in the "Add or Remove Programs" (ARPPRODUCTICON
	  icon):

	  @verbatim
	  ({
		"startMenuShortcut_7a40770c84216f8b0": {"Id": "myIcon.exe" },
		"desktopShortcut_7a40770c84216f8b0": {"Id": "myIcon.exe" },
		"ARPPRODUCTICON": {"Id": "myIcon.exe" },
	  })
	  @endverbatim

	  Optional property `IconIndex` is recognized for shortuct elements.
	  */
	property var iconAssignments: ({})

	/**
	  Whether to create MajorUpgrade element.
	  */
	property bool majorUpgrade: true

	/**
	  Merge modules map. The map should contain merge modules that should be used for feature associated with specific product.
	  Product `<product name>` must be listed in @a featuredProducts. If no products are featured, then name of the WixInstaller
	  product can be used.

	  @verbatim
	  ({
		"<product name>": [<path to msm file>,]
		},
	  })
	  @endverbatim
	  */
	property var mergeModules: {}

	Depends { name: "cpp" }

	// For cultures see: https://wixtoolset.org/documentation/manual/v3/wixdev/extensions/localized_extensions.html, https://github.com/wixtoolset/issues/issues/5685, https://github.com/wixtoolset/issues/issues/3007
	Depends { name: "wix" }
	wix.linkerFlags: [
		"-b", qbs.installRoot + qbs.installPrefix,
		"-ext", "WixUIEXtension",
		"-cultures:" + cultures
	]

	Rule {
		multiplex: true
		inputsFromDependencies: product.inputFileTags
		//<WixInstaller-1.workaround target="windeployqt" cause="bug">
		// Run wxs generation after wxl, because there is race condition due to workaround - both functions: dumpProductToWxl() and
		// dumpProductToWxs() call buildFileComponentGroups(), which attempts to copy missing files.
		auxiliaryInputs: ["wxl"]
		//</WixInstaller-1.workaround>

		prepare: {
			var cmd = new JavaScriptCommand();
			cmd.description = 'generating ' + output.filePath
			cmd.highlight = 'codegen';
			var tagName = "qml"
			cmd.sourceCode = function() {
				if (!File.exists(product.wixUpgradeCodeFile)) {
					var upgradeCodeFile = new TextFile(product.wixUpgradeCodeFile, TextFile.WriteOnly)
					try {
						upgradeCodeFile.writeLine(Wix.createGuid())
					} finally {
						upgradeCodeFile.close()
					}
				}
				upgradeCodeFile = new TextFile(product.wixUpgradeCodeFile, TextFile.ReadOnly)
				var upgradeCodeGuid = upgradeCodeFile.readLine()
				upgradeCodeFile.close()

				var wxsFile = Xml.DomDocument()
				Wix.dumpProductToWxs(product, inputs, upgradeCodeGuid, wxsFile)
				wxsFile.save(output.filePath, 4)

				//<WixInstaller-2.workaround target="Qbs" cause="missing">
				// Module qbs.Xml writes strings as they come, therefore as a workaround TextFile codec is used to convert WXS
				// file to proper codepage.
				if (!["UTF-8", "WINDOWS-1252", "ISO-8859-1"].contains(product.xmlCodepage.toUpperCase())) {
					var wxsTextFile = TextFile(output.filePath, TextFile.ReadOnly)
					try  {
						var wxsContent = wxsTextFile.readAll()
					} finally {
						wxsTextFile.close()
					}
					wxsTextFile = TextFile(output.filePath, TextFile.WriteOnly)
					wxsTextFile.setCodec(product.xmlCodepage)
					try {
						wxsTextFile.write(wxsContent)
					} finally {
						wxsTextFile.close()
					}
				}
				//</WixInstaller-2.workaround>
			}
			return [cmd]
		}

		Artifact {
			filePath: product.wxsArtifact
			fileTags: ["wxs"]
		}
	}

	Rule {
		multiplex: true
		inputsFromDependencies: product.inputFileTags

		prepare: {
			var cmd = new JavaScriptCommand();
			cmd.description = 'generating ' + output.filePath
			cmd.highlight = 'codegen';
			cmd.sourceCode = function() {
				var wxlFile = Xml.DomDocument()
				Wix.dumpProductToWxl(product, inputs, wxlFile)
				wxlFile.save(output.filePath, 4)
			}
			return [cmd]
		}

		Artifact {
			filePath: product.wxlArtifact
			fileTags: ["wxl"]
		}
	}
}

//(c)C: Copyright © 2022-2024, Michał Policht <michal@policht.pl>. All rights reserved.
//(c)C: SPDX-License-Identifier: LGPL-3.0-or-later OR MIT
//(c)C: This file is a part of CuteHMI.
//(c)C: CuteHMI is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
//(c)C: CuteHMI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
//(c)C: You should have received a copy of the GNU Lesser General Public License along with CuteHMI.  If not, see <https://www.gnu.org/licenses/>.
//(c)C: Additionally, this file is licensed under terms of MIT license as expressed below.
//(c)C: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//(c)C: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//(c)C: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
